package models

import (
	"bytes"
	"encoding/json"
	"fmt"
	"gitchina/kly_mall/data"
	"gitchina/kly_mall/errors"
	"gitchina/kly_mall/util"
	"io"
	"io/ioutil"
	"mime/multipart"
	"strings"
	"time"

	"github.com/pborman/uuid"

	"github.com/aliyun/aliyun-oss-go-sdk/oss"
)

const (
	FUNC_GET_CATEGORIE_LIST = "mall.getCategories"
	FUNC_GET_ATTRIBUTE_LIST = "mall.getAttributes"
	FUNC_GET_GOODS_LIST     = "mall.getGoodsList"
	FUNC_GET_GOODS          = "mall.getGoods"
	FUNC_GET_GOODS_ATTRS    = "mall.getGoodsAttrs"
	FUNC_MAKE_ORDER         = "mall.makeOrder"
	FUNC_GET_SHIPPING_INFO  = "mall.getShippingInfo"
	FUNC_GET_ORDER          = "mall.getOrder"
	FUNC_CHARGE             = "mall.charge"
	FUNC_UPLOAD_IMG         = "mall.uploadImg"
	FUNC_UPLOAD_LIST        = "mall.getOrderList"
)

//Mall ...
type Mall struct {
}

//GetCategories ...
func (m Mall) GetCategoryList() (list []Category) {
	redis := util.NewRedis(RedisAddr())
	resp := redis.Cmd("get", CATEGORY_CACHE_KEY)
	if resp.Err != nil {
		util.WriteLog(errors.New(FUNC_GET_CATEGORIE_LIST, resp.Err.Error()))
		return
	}
	jsond, err := resp.Bytes()
	if err != nil {
		util.Event{Name: util.NOTIFY_CATEGORY_LOAD}.Pub()
		util.WriteLog(errors.New(FUNC_GET_CATEGORIE_LIST, err.Error()))
		return
	}
	if err := json.Unmarshal(jsond, &list); err != nil {
		util.WriteLog(errors.New(FUNC_GET_CATEGORIE_LIST, err.Error()))
		return
	}
	return
}

func (m Mall) GetAttributeList(categoryID int64) (list []Attribute) {
	redis := util.NewRedis(RedisAddr())
	resp := redis.Cmd("get", fmt.Sprintf("%s_%d", CATEGORY_ATTRIBUTE_CACHE_KEY, categoryID))
	if resp.Err != nil {
		util.WriteLog(errors.New(FUNC_GET_ATTRIBUTE_LIST, resp.Err.Error()))
		return
	}
	jsond, err := resp.Bytes()
	if err != nil {
		util.Event{Name: util.NOTIFY_ATTRIBUTE_LOAD, Data: categoryID}.Pub()
		util.WriteLog(errors.New(FUNC_GET_ATTRIBUTE_LIST, err.Error()))
		return
	}
	if err := json.Unmarshal(jsond, &list); err != nil {
		util.WriteLog(errors.New(FUNC_GET_ATTRIBUTE_LIST, err.Error()))
		return
	}
	return
}

func (m Mall) GetGoodsList(categoryID int64, pageNo, pageSize int) GoodsList {
	var err error
	var goodsList []data.Goods

	list := GoodsList{TotalCount: 0, List: make([]interface{}, 0)}

	if list.TotalCount, goodsList, err = data.GetGoodsList(categoryID, pageNo, pageSize); err != nil {
		util.WriteLog(errors.New(FUNC_GET_GOODS_LIST, err.Error()))
		return list
	}

	list.List = util.MapFrom(goodsList).Take("ID", "SN", "Name", "NameStyle", "ClickCount",
		"Number", "Weight", "MarketPrice", "ShopPrice", "Brief", "Desc",
		"Thumb", "Img", "OriginalImg", "IsReal", "ExtensionCode",
		"IsShipping", "IsBest", "IsNew", "IsHot").To(Goods{}).([]interface{})

	return list
}

func (m Mall) GetGoods(goodsID int64) (goods *Goods) {
	var err error
	var obj data.Goods

	if obj, err = data.GetGoodsByID(goodsID); err != nil {
		util.WriteLog(errors.New(FUNC_GET_GOODS, err.Error()))
		return
	}
	goods = util.MapFrom(obj).Take("ID", "CategoryID", "SN", "Name", "NameStyle", "ClickCount",
		"Number", "Weight", "MarketPrice", "ShopPrice", "Brief", "Desc",
		"Thumb", "Img", "OriginalImg", "IsReal", "ExtensionCode",
		"IsShipping", "IsBest", "IsNew", "IsHot").To(Goods{}).(*Goods)

	return
}

func (m Mall) GetGoodsAttrs(goodsID int64) (attrs interface{}) {
	var err error
	var list []data.GoodsAttribute

	if list, err = data.GetGoodsAttrsByGoodsID(goodsID); err != nil {
		util.WriteLog(errors.New(FUNC_GET_GOODS_ATTRS, err.Error()))
		return
	}
	attrs = util.MapFrom(list).Take("Name", "Value").To(GoodsAttribute{})
	return
}

func (m Mall) MakeOrder(memberID ID, rq MakeOrderRQ) (no string, err error) {
	var goods data.Goods

	if _, _, _, _, err = memberID.Decode(); err != nil {
		util.WriteLog(errors.New(FUNC_MAKE_ORDER, "invalid member").Append("id", memberID))
		err = errors.Raw(errors.MBR_INVAID)
		return
	}

	if goods, err = data.GetGoodsByID(rq.GoodsID); err == nil {
		var tx *data.Trans
		order := util.MapFrom(rq).Take("Consignee", "Country", "Province",
			"City", "District", "Address", "ZipCode", "Tel", "Mobile", "Email",
			"Postscript", "ShippingID", "ShippingName", "ShippingCode").To(data.Order{}).(*data.Order)
		order.UserID = string(memberID)
		order.Amount = goods.ShopPrice * rq.GoodsNumber
		order.PayFee = goods.ShopPrice * rq.GoodsNumber
		order.AddTime = time.Now().Format("2006-01-02 15:04:05")
		if tx, err = data.BeginTx(); err == nil {
			defer func() {
				tx.End(err == nil)
				if err != nil {
					util.WriteLog(errors.New(FUNC_MAKE_ORDER, err.Error()))
					err = errors.Raw(errors.ORDER_MAKE_FAILED)
				}
			}()

			og := util.MapFrom(goods).
				Take("Name", "SN", "MarketPrice", "IsReal", "ExtensionCode").
				To(data.OrderGoods{}).(*data.OrderGoods)
			og.GoodsID = rq.GoodsID
			og.GoodsPrice = goods.ShopPrice
			og.Number = rq.GoodsNumber
			no, err = tx.CreateOrder(og, order)
		}
	}

	return
}

func (m Mall) GetShippingInfo(memberID string) (info *ShippingInfo) {
	if order, err := data.GetLastOrder(memberID); err == nil {
		info = util.MapFrom(order).Take("Consignee", "Country", "Province",
			"City", "District", "Address", "ZipCode", "Tel", "Mobile",
			"Email", "ShippingID", "ShippingName", "ShippingCode").
			To(ShippingInfo{}).(*ShippingInfo)
	} else {
		util.WriteLog(errors.New(FUNC_GET_SHIPPING_INFO, err.Error()))
	}
	return
}

func (m Mall) GetOrder(orderNO, memberID string) (order *Order, err error) {
	var e error
	var o data.Order

	if o, e = data.GetOrderByNO(orderNO, memberID); e == nil {
		order = util.MapFrom(o).Take("ID", "NO", "Status", "ShippingStatus",
			"PayStatus", "Consignee", "Country", "Province", "City",
			"District", "Address", "ZipCode", "Tel", "Mobile", "Email",
			"Postscript", "ShippingName", "ShippingCode", "Amount", "AddTime").
			To(Order{}).(*Order)
	} else {
		util.WriteLog(errors.New(FUNC_GET_ORDER, err.Error()))
		err = errors.Raw(errors.ORDER_NOT_FOUND)
	}
	return
}

func (m Mall) GetOrderList(memberID string, pageNo, pageSize int) (orderList OrderList) {
	var err error
	var list []data.OrderInfo

	if list, orderList.TotalCount, err = data.GetOrderList(memberID, pageNo, pageSize); err == nil {
		orderList.List = util.MapFrom(list).Take(
			"NO", "Status", "ShippingStatus", "PayStatus", "Amount",
			"PayFee", "AddTime", "GoodsName", "GoodsThumb").To(OrderInfo{})
	} else {
		util.WriteLog(errors.New(FUNC_UPLOAD_LIST, err.Error()))
	}
	return
}

func (m Mall) Charge(orderNO string, memberID ID) error {
	var err error
	var mid int64
	var prepareOk bool
	var tx *data.Trans
	var order data.Order
	var goods data.OrderGoods

	if _, _, _, mid, err = memberID.Decode(); err != nil {
		util.WriteLog(errors.New(FUNC_CHARGE, "invalid member").Append("id", memberID))
		return errors.Raw(errors.MBR_INVAID)
	}

	if order, err = data.GetOrderByNO(orderNO, string(memberID)); err != nil {
		util.WriteLog(errors.New(FUNC_CHARGE, err.Error()).Append("order_id", orderNO))
		return errors.Raw(errors.ORDER_NOT_FOUND)
	}

	if goods, err = data.GetOrderGoodsByID(order.ID); err != nil {
		util.WriteLog(errors.New(FUNC_CHARGE, err.Error()).Append("order_id", orderNO))
		return errors.Raw(errors.ORDER_NOT_FOUND)
	}

	if prepareOk, err = data.PrepareToCharge(int(mid), goods.ID, goods.Number, order.PayFee); err != nil {
		util.WriteLog(errors.New(FUNC_CHARGE, err.Error()).
			Append("member_id", memberID).
			Append("goods_id", goods.ID))
		err = errors.Raw(errors.ORDER_NOT_FOUND)
	} else {
		if prepareOk {
			if tx, err = data.BeginTx(); err == nil {
				defer func() {
					tx.End(err == nil)
				}()

				if err = tx.Charge(string(memberID), order, goods); err != nil {
					util.WriteLog(errors.New(FUNC_CHARGE, err.Error()).
						Append("member_id", memberID).
						Append("order_id", goods.ID))
					err = errors.Raw(errors.CHARGE_FAILED)
				}
			} else {
				util.WriteLog(errors.New(FUNC_CHARGE, err.Error()).
					Append("member_id", memberID).
					Append("order_id", goods.ID))
				err = errors.Raw(errors.CHARGE_FAILED)
			}
		} else {
			err = errors.Raw(errors.POINT_OR_GOODS_NOT_ENOUGH)
		}
	}

	return err
}

func (m Mall) UploadImg(
	cdn, bucketName, endpoint string,
	maxSizeAllowed int,
	file io.Reader,
	header *multipart.FileHeader) (GoodsImage, int) {

	var err error
	var ext string
	var img []byte

	var client *oss.Client
	var bucket *oss.Bucket

	code := 200
	goodsImg := GoodsImage{}

	akid := "LTAIQEtPo8rFH3xL"
	aks := "EPtQJjJYX8fBww68fstbcs6TMDTSjm"

	if ext, code = getImgExt(header.Header.Get("Content-Type")); code == 200 {
		if img, err = ioutil.ReadAll(file); err != nil {
			return goodsImg, 400
		}

		if len(img) > maxSizeAllowed {
			return goodsImg, 413
		}

		if len(img) == 0 {
			return goodsImg, 411
		}

		if client, err = oss.New(endpoint, akid, aks); err == nil {
			if bucket, err = client.Bucket(bucketName); err == nil {
				fn := fmt.Sprintf("shopmall/%s.%s", strings.Replace(uuid.New(), "-", "", -1), ext)
				if err = bucket.PutObject(fn, bytes.NewBuffer(img)); err == nil {
					goodsImg.Original = fmt.Sprintf("%s%s", cdn, fn)
					goodsImg.Large = fmt.Sprintf("%s?%s", goodsImg.Original, "x-oss-process=image/resize,m_mfit,h_108,w_108")
					goodsImg.Thumb = fmt.Sprintf("%s?%s", goodsImg.Original, "x-oss-process=image/resize,m_mfit,h_108,w_108")
				}
			}
		}

		if err != nil {
			return goodsImg, 400
		}
	}
	return goodsImg, code
}

func getImgExt(contentType string) (ext string, code int) {
	switch strings.ToLower(contentType) {
	case "image/jpg", "image/jpeg":
		ext = "jpg"
	case "image/png":
		ext = "png"
	}

	if ext == "" {
		code = 415
	} else {
		code = 200
	}
	return
}
